/*
  author Sylvain Bertrand <digital.ragnarok@gmail.com>
  Protected by GNU Affero GPL v3 with some exceptions.
  See README at root of alga tree.
*/
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/delay.h>

#include <alga/alga.h>
#include <alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/dp.h>
#include <alga/edid.h>
#include <alga/amd/atombios/atb.h>
#include <alga/amd/atombios/dce.h>
#include <alga/amd/dce4/dce4.h>
#include <alga/amd/dce4/dce4_dev.h>

#include "dce4.h"
#include "dp_link_train.h"

int sink_timings(struct dce4 *dce, unsigned i, struct alga_timing **ts)
{
	int r;

	if (dce->dps[i].edid == NULL) {
		r = alga_dp_safe_timing(ts);
		if (r != 0)
			return r;
	}

	r = alga_edid_timings(dce->ddev.dev, dce->dps[i].edid, ts);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4: unable to collect edid timings "
							"for dp%u\n", i);
		r = -DCE4_ERR;
		goto err;
	}
err:
	return r;
}

int pixel_fmts(struct dce4 *dce, unsigned i, enum alga_pixel_fmt **fmts)
{
	int bpc;
	int r;

	r = alga_pixel_fmts_init(fmts);
	if (r < 0)
		return -DCE4_ERR;

	if (dce->dps[i].edid == NULL) {
		r = alga_pixel_fmts_add(ALGA_ARGB6666, fmts);
		if (r < 0) {
			r = -DCE4_ERR;
			goto err;
		}
	}

	bpc = alga_edid_bpc(dce->ddev.dev, dce->dps[i].edid);	
	if (bpc == 0) {
		r = alga_pixel_fmts_add(ALGA_ARGB6666, fmts);
		if (r < 0) {
			r = -DCE4_ERR;
			goto err;
		}
	}

	switch (bpc) {
	case 16:
	case 14:
	case 12:
	case 10:
		alga_pixel_fmts_add(ALGA_ARGB2101010, fmts);
	case 8:
		alga_pixel_fmts_add(ALGA_ARGB8888, fmts);
	case 6:
		alga_pixel_fmts_add(ALGA_ARGB6666, fmts);
	}
	return 0;

err:
	kfree(*fmts);
	return r;
}

int sink_mode_set(struct dce4 *dce, unsigned i, u64 fb,
			enum alga_pixel_fmt pixel_fmt, struct alga_timing *t)
{
	struct atombios *atb;
	int r;

	atb = dce->ddev.atb;

	atb_lock(atb, true);

	/* prepare encoder and transmitter */
	r = atb_trans_link_output_off(atb, i);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to switch off "
						"transmitter link output\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_enc_video(atb, i, false);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to switch off "
						"encoder video output\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	if (dce->dps[i].edp) {
		r = atb_trans_link_pwr(atb, i, true);
		if (r != 0) {
			dev_err(dce->ddev.dev, "dce4:dp%u: unable to enable "
						"power pins on connector\n", i);
			r = -DCE4_ERR;
			goto unlock_atb;
		}
		msleep(300);/* follow timing specs from AMD code */
	}

	r = atb_enc_crtc_src(atb, i);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to link crtc and "
						"encoder together\n", i);	
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	/* prepare crtc */
	r = atb_crtc_lock(atb, i, true);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to lock the crtc\n",
									i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_blank(atb, i, true);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to blank the crtc\n",
									i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_mem_req(atb, i, false);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to disable crtc "
							"memory requests\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc(atb, i, false);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to disable the "
								"crtc\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	/* crtc mode set */
	r = atb_crtc_virtual_pixel_clk(atb, i, t->pixel_clk);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to program the "
						"virtual pixel clock\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_timing(atb, i, t);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to program timings\n",
									i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = dce->ddev.crtc_fb(dce->ddev.dev, i, fb, pixel_fmt, t->h, t->v,
									t->h);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to setup the frame "
								"buffer\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_overscan(atb, i);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to setup the "
							"overscan\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_scaler(atb, i);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to setup the "
								"scaler\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	/* encoder and transmitter mode set */
	r = atb_trans_link_off(atb, i);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to disable the "
						"transmitter link\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_enc_setup(atb, i, dce->dps[i].lanes_n,
			dce->dps[i].link_rate, alga_pixel_fmts_bpc[pixel_fmt],
								t->pixel_clk);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to setup the "
								"encoder\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_trans_link_on(atb, i, dce->dps[i].lanes_n, t->pixel_clk);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to enable the "
						"transmitter link\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	/* crtc commit */
	r = atb_crtc(atb, i, true);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to enable the "
								"crtc\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_mem_req(atb, i, true);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to enable the crtc "
							"memory requests\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_blank(atb, i, false);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to unblank the "
								"crtc\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	dce->ddev.lut(dce->ddev.dev, i);

	r = atb_crtc_lock(atb, i, false);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to unlock the crtc\n",
									i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	/* encoder and transmitter commit */
	r = atb_trans_link_output_on(atb, i, dce->dps[i].lanes_n, t->pixel_clk);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to enable the "
						"transmitter link output\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	/* XXX: skipped switching on again power */

	r = dp_link_train(dce, i);
	if (r < 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to train the link\n",
									i);
		goto unlock_atb;
	}

	r = atb_enc_video(atb, i, true);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to enable the video "
						"output on encoder\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}
unlock_atb:
	atb_lock(atb, false);
	return r;
}
